#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <glib.h>

#include "../vcd-common-api/vcd-usb-redir.h"
#include "../vcd-common-api/vcd-log.h"
#include "spice-client.h"
#include "vcd-usb-network.h"
#include "vcd-usb-common.h"
#include "../vcd-common-api/vcd-topbar/vcd-topbar.h"
#include "../vcd-common-api/vcd-conf/vcd-conf.h"

#ifdef __WIN32__
#include <windows.h>
#include <tlhelp32.h>
#endif

static VcdUsbCtx g_vcd_usb_ctx;

static VcdUsbRedir *redir;

static char* spice_host;
static char* spice_port;
static char* spice_pass;
static char* spice_proxy;
static int   parent_port;
static int   is_send_monitor_config;
static int   is_use_spice_audio;
static int   is_use_spice_topbar;
static int   is_disable_spice_usb_btn;

static GOptionEntry cmd_entries[] = {
    {
        .long_name        = "spice-host",
        .short_name       = 'h',
        .arg              = G_OPTION_ARG_STRING,
        .arg_data         = &spice_host,
        .description      = "host of spice",
    },
    {
        .long_name        = "spice-port",
        .short_name       = 'p',
        .arg              = G_OPTION_ARG_STRING,
        .arg_data         = &spice_port,
        .description      = "port of spice",
    },
    {
        .long_name        = "spice-password",
        .short_name       = 'w',
        .arg              = G_OPTION_ARG_STRING,
        .arg_data         = &spice_pass,
        .description      = "password of spice",
    },
    {
        .long_name        = "spice-proxy",
        .short_name       = 'x',
        .arg              = G_OPTION_ARG_STRING,
        .arg_data         = &spice_proxy,
        .description      = "proxy of spice",
    },
    {
        .long_name        = "parent-port",
        .short_name       = 's',
        .arg              = G_OPTION_ARG_INT,
        .arg_data         = &parent_port,
        .description      = "port listen in parent process",
    },
    {
        .long_name        = "monitor-config",
        .short_name       = 'm',
        .arg              = G_OPTION_ARG_NONE,
        .arg_data         = &is_send_monitor_config,
        .description      = "send monitor config",
    },
    {
        .long_name        = "spice-audio",
        .short_name       = 'a',
        .arg              = G_OPTION_ARG_NONE,
        .arg_data         = &is_use_spice_audio,
        .description      = "use spice audio",
    },
    {
        .long_name        = "spice-topbar",
        .short_name       = 't',
        .arg              = G_OPTION_ARG_NONE,
        .arg_data         = &is_use_spice_topbar,
        .description      = "use spice topbar",
    },
    {
        .long_name        = "disable-spice-usb-btn",
        .short_name       = 'd',
        .arg              = G_OPTION_ARG_NONE,
        .arg_data         = &is_disable_spice_usb_btn,
        .description      = "use disable spice-topbar usb btn",
    },
     {
        /* end of list */
    }
};

static void send_monitor_config(SpiceChannel *channel);



/**
 * @brief check_is_process_exist
 * @param process_name
 * @param is_kill_it
 * @return true: if process exist    false:not exist
 */
static gboolean check_is_process_exist(char *process_name, gboolean is_kill_it)
{
    gboolean bFind = FALSE;
#ifdef __WIN32__
    HANDLE hProcessSnap;
    HANDLE hProcess;
    PROCESSENTRY32 pe32;
    DWORD dwPriorityClass;

    // Take a snapshot of all processes in the system.
    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    // Set the size of the structure before using it.
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Retrieve information about the first process,
    // and exit if unsuccessful
    if (!Process32First(hProcessSnap, &pe32))
    {
        CloseHandle(hProcessSnap);          // clean the snapshot object
        return FALSE;
    }

    char szPeProcName[_MAX_PATH] = {0};

    // Now walk the snapshot of processes, and
    // display information about each process in turn
    do
    {
        // Retrieve the priority class.
        sprintf(szPeProcName, "%s", pe32.szExeFile );

        //pe32.th32ProcessID
        if (0 == strcmp(szPeProcName, process_name))
        {
            int currProcessId = getpid();
            if (currProcessId != pe32.th32ProcessID) {

                bFind = TRUE;

                if (is_kill_it) {
                    //加了下面代码，Vclient无法退出
                    M_DEBUG("ready to kill process:%s", szPeProcName);
                    HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
                    TerminateProcess(hProcess,0);  //杀死进程
                    CloseHandle(hProcess);

                    char str_cmd[100] = {0};
                    sprintf(str_cmd, "/PID %d", pe32.th32ProcessID);
                    ShellExecute(NULL, "open", "taskkill.exe", str_cmd, "", SW_HIDE);
                }

                break;
            }
        }
    } while (Process32Next(hProcessSnap, &pe32));

    CloseHandle(hProcessSnap);

#endif
    return bFind;
}


void _vcd_usb_main_on_clicked_usb_redir(GtkWidget* widget,gpointer priv){
    if(redir)
        vcd_usb_redir(NULL,redir);
}

void _vcd_usb_main_on_clicked_close(GtkWidget* widget,gpointer priv){
    check_is_process_exist("verystream.exe",TRUE);
}

void _vcd_usb_main_on_clicked_fullscreen(GtkWidget* widget,gpointer priv){
    VcdMsgHeader header = {0};
    header.type = VCD_MSG_FULLSCREEN;
    header.length = sizeof(VcdMsgHeader);
    vcd_usb_network_send(priv,&header,sizeof(header));
}

void _vcd_usb_main_on_clicked_reconnect(GtkWidget* widget,gpointer priv){
    VcdMsgHeader header = {0};
    header.type = VCD_MSG_RECONNECT;
    header.length = sizeof(VcdMsgHeader);
    vcd_usb_network_send(priv,&header,sizeof(header));
}

static gboolean parse_option_args(int argc, char* argv[]) {
    GOptionContext *context;
    GError *error = NULL;

    context = g_option_context_new(" - start vcd-usb-main as a child process");
    g_option_context_add_main_entries(context, cmd_entries, NULL);
    g_option_context_set_ignore_unknown_options(context, TRUE);
    if (!g_option_context_parse(context, &argc, &argv, &error)) {
        //LOG_ERROR("option parsing failed: %s\n", error->message);
        return FALSE;
    }

    g_option_context_free(context);

    return TRUE;
}

static gboolean _vcd_topbar_new(VcdTopbarCtx* topbarctx){

    GtkWidget* topbar_toplevel = vcd_topbar_new(topbarctx);

    VcdUsbCtx* vuc = topbarctx->priv;
    if(vuc)
        vuc->topbar_widget = topbar_toplevel;
    return FALSE;
}

static gboolean _vcd_tobar_show_hide(gpointer priv){

    VcdUsbCtx* vuc = priv;
    if(!vuc || !vuc->topbar_widget)
        return FALSE;

    GtkWidget* toplevel = vuc->topbar_widget;
    gboolean   b        = gtk_widget_get_visible(toplevel);
    gtk_widget_set_visible(toplevel,!b);

    return  FALSE;
}

static void _on_recved_msg_from_parent(VcdMsgHeader* msg,gpointer vcd_usb_ctx){

    VcdUsbCtx* vuc = vcd_usb_ctx;

    if(msg->type == VCD_MSG_DEFAULT){
        if(redir)
            vcd_usb_redir(NULL,redir);
    }else if(msg->type == VCD_MSG_SHOW_SPICE_TOPBAR){

        TopbarPostion position = POSITION_TOP;
        gboolean is_show_topbar = TRUE;
        if(g_vcd_conf){
            position = g_vcd_conf->topbar_position;
            is_show_topbar = g_vcd_conf->is_show_topbar;
        }

        VcdMsgTopbar* topmsg = (VcdMsgTopbar*)msg;

        VcdTopbarCtx* topbarctx = calloc(1,sizeof(VcdTopbarCtx));
        topbarctx->is_show_topbar = is_show_topbar;
        topbarctx->parent_winid = topmsg->parent_window_xid;
        topbarctx->disable_btn_usbredir = is_use_spice_audio;
        topbarctx->disable_btn_usbredir = is_disable_spice_usb_btn;
        topbarctx->disable_btn_shutdown = TRUE;
        topbarctx->position = position;
        topbarctx->on_usb_redir = _vcd_usb_main_on_clicked_usb_redir;
        topbarctx->on_close = _vcd_usb_main_on_clicked_close;
        topbarctx->on_fullscreen = _vcd_usb_main_on_clicked_fullscreen;
        topbarctx->on_reconnect = _vcd_usb_main_on_clicked_reconnect;
        topbarctx->priv = vcd_usb_ctx;
        //vcd_topbar_new(topbarctx);
        g_idle_add((GSourceFunc)_vcd_topbar_new,topbarctx);
    }else if(msg->type == VCD_MSG_SHOW_HIDE_TOPBAR){
        g_idle_add((GSourceFunc)_vcd_tobar_show_hide,vcd_usb_ctx);
    }else{

    }

    free((char*)msg);
}

static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,gpointer data)
{
    switch (event) {
    case SPICE_CHANNEL_CLOSED:
        M_INFO("channel closed process exit main:%d usb:%d playback:%d",SPICE_IS_MAIN_CHANNEL(channel),SPICE_IS_USBREDIR_CHANNEL(channel),SPICE_IS_PLAYBACK_CHANNEL(channel));
        char *cmd = "killall verystream";//make sure verystream quit when topbar quit
        system(cmd);
        gtk_main_quit();
        break;
    case SPICE_CHANNEL_OPENED:
        if(SPICE_IS_USBREDIR_CHANNEL(channel)){
            M_INFO("channel USBREDIR opened");
        }else if(SPICE_IS_MAIN_CHANNEL(channel)){
            M_INFO("channel MAIN opened");
        }else if(SPICE_IS_PLAYBACK_CHANNEL(channel)){
            M_INFO("channel PLAYBACK opened");
        }
        break;
    default:
        M_ERROR("CHANNEL_ERROR type:%d main:%d usb:%d playback:%d",event,SPICE_IS_MAIN_CHANNEL(channel),SPICE_IS_USBREDIR_CHANNEL(channel),SPICE_IS_PLAYBACK_CHANNEL(channel));
        break;
    }
}

static void adapter_width_height(int w,int h,int* nw,int* nh){
    int maxW = 2560;
    int maxH = 1600;

    if(w <= maxW && h <= maxH){
        *nw = w;
        *nh = h;
        return;
    }

    float sw = w * 1.0 / maxW;
    float sh = h * 1.0 / maxH;
    float ss = sw > sh ? sw : sh;

    int tw = w / ss;
    int th = h / ss;

    if(tw % 2 != 0)
        tw++;
    if(th % 2 != 0)
        th++;

    *nw = tw;
    *nh = th;
}

/***
 *   only send when use '-m'
 *   qxl max width  is 2560
 *   qxl max height is 1600
 **/
static void send_monitor_config(SpiceChannel *channel){

    if(!is_send_monitor_config)
        return;

    int id = 0;
    int width = 2560;
    int height = 1600;
    int w,h;

    vcd_topbar_get_screen_base_size(&w,&h);

#if 0
    width = w;
    height = h;
#else
    vcd_topbar_adapter_spice_size(w,h,&width,&height);
#endif

    spice_main_channel_update_display_enabled(SPICE_MAIN_CHANNEL(channel),id,TRUE,FALSE);
    spice_main_channel_update_display(SPICE_MAIN_CHANNEL(channel),id,0,0,width,height,TRUE);
    spice_main_channel_send_monitor_config(SPICE_MAIN_CHANNEL(channel));

    M_DEBUG("send_monitor_config width:%d,height:%d",width,height);
}

static void main_agent_update(SpiceChannel *channel, gpointer data){

    send_monitor_config(channel);

    //gboolean agent_connected = FALSE;
    //g_object_get(channel, "agent-connected", &agent_connected, NULL);

    //spice_main_channel_request_mouse_mode(channel,SPICE_MOUSE_MODE_CLIENT);

    //gpointer object;
    //object = spice_gtk_session_get(data);
    //g_object_set(object,"auto-clipboard",1,NULL);
}

static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
    if (SPICE_IS_MAIN_CHANNEL(channel)) {
        //M_INFO("main channel connected");

        g_signal_connect(channel, "main-agent-update",G_CALLBACK(main_agent_update), s);

        spice_main_channel_add_channel_to_white_list(SPICE_MAIN_CHANNEL(channel),SPICE_CHANNEL_MAIN);

        if(is_use_spice_audio)
            spice_main_channel_add_channel_to_white_list(SPICE_MAIN_CHANNEL(channel),SPICE_CHANNEL_PLAYBACK);

        if(!is_disable_spice_usb_btn){
            spice_main_channel_add_channel_to_white_list(SPICE_MAIN_CHANNEL(channel),SPICE_CHANNEL_USBREDIR);
            redir = create_vcd_usb_redir(s, NULL);
        }

        g_vcd_usb_ctx.usb_rdir = redir;
        vcd_usb_network_start(&g_vcd_usb_ctx);

    }else if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
        //M_DEBUG("new usbredir channel");
    }
    g_signal_connect(channel, "channel-event",G_CALLBACK(main_channel_event), s);
}

static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
    gtk_main_quit();
}

static void connection_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
    gtk_main_quit();
}

static void spice_connect(const char *host, const char *port, const char *password, const char *proxy){

    SpiceSession     *session;

    session = spice_session_new();
    g_signal_connect(session, "channel-new",G_CALLBACK(channel_new), NULL);
    g_signal_connect(session, "channel-destroy",G_CALLBACK(channel_destroy), NULL);
    g_signal_connect(session, "disconnected",G_CALLBACK(connection_destroy), NULL);

    g_object_set(session, "host", host, NULL);
    g_object_set(session, "port", port, NULL);
    g_object_set(session, "password", password, NULL);
    g_object_set(session, "proxy", proxy, NULL);

    init_usb_redir_operate_callback(session, host, port);

    spice_session_connect(session);
}


void init_log(int argc,char* argv[]){

    const char* filepath = argv[0];

    if(!filepath)
        return;
    if(!g_strrstr(filepath,"vcd-usb"))
        return;

#ifdef __WIN32__
    const char* dir_name   = g_path_get_dirname(filepath);
    const char* log4c_path = g_strconcat(dir_name,"/../etc/","log4crc",NULL);
    const char* log_dir    = g_strconcat(dir_name,"/../log/usb/",NULL);

    char* log4crc          = g_canonicalize_filename(log4c_path,NULL);
    char* logdir           = g_canonicalize_filename(log_dir,NULL);
#elif __linux__
    char* log4crc = "/etc/log4crc";
    char* logdir  = "/var/log/usb";
#else
    char* log4crc = "";
    char* logdir  = "";
#endif
    vcd_log_set_filename("vclient-u");
    vcd_log_init_with_logdir(log4crc,logdir);
}

/**
 * usage: vcd-usb.exe -h 127.0.0.1 -p 61000 -w 123 -x xxxxx -s 23800
 *
 * @brief main
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char *argv[])
{
    init_log(argc,argv);

#if 0
    for(int i = 0;i < argc;i++){
        M_DEBUG("argc:%d,argv:%s",i,argv[i]);
    }
#endif

    // init gtk
    gtk_init(&argc, &argv);

    if(!parse_option_args(argc,argv)){
        return -1;
    }

    M_INFO("vcd-usb main run");
    M_DEBUG("h:%s p:%s w:%s x:%s s:%d m:%d a:%d t:%d d:%d",spice_host,spice_port,spice_pass,spice_proxy,parent_port,is_send_monitor_config,is_use_spice_audio,is_use_spice_topbar,is_disable_spice_usb_btn);

    // set g_vcd_usb_ctx var
    g_vcd_usb_ctx.parent_port = parent_port;
    g_vcd_usb_ctx.is_use_spice_topbar = is_use_spice_topbar;
    g_vcd_usb_ctx.msg_callback = _on_recved_msg_from_parent;

    read_vcd_conf();

#if 0
    const char* host = "192.168.199.162";
    const char* port = "61000";
    const char* pass = "123";
    const char* proxy= NULL;
#else
    const char* host = spice_host;
    const char* port = spice_port;
    const char* pass = spice_pass;
    const char* proxy= spice_proxy;
#endif

    spice_connect(host,port,pass,proxy);

    gtk_main();

    M_INFO("vcd-usb main end");
    return 0;
}
